// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#ifndef NL_TRANSPORT_CLASS_H
#define NL_TRANSPORT_CLASS_H


//
// Includes
//

#include "nel/misc/types_nl.h"
#include "nel/misc/stream.h"
#include "nel/misc/entity_id.h"
#include "nel/misc/sheet_id.h"
#include "nel/misc/variable.h"

#include "unified_network.h"
#include "message.h"

#include <vector>
#include <string>

namespace NLNET {

    //
    // Macros
    //

    /**
    * Use this macro to register a class that can be transported in the init of your program.
    */

#define TRANSPORT_CLASS_REGISTER(_c) \
    static _c _c##Instance; \
    CTransportClass::registerClass (_c##Instance);

#define TRANSPORT_CLASS_INSTANCE() \
    CTransportClass::init();

#define NETTC_INFO if (!VerboseNETTC.get()) {} else nlinfo
#define NETTC_DEBUG if (!VerboseNETTC.get()) {} else nldebug
    extern NLMISC::CVariable<bool> VerboseNETTC;


    //
    // Classes
    //

    /**
    * You have to inherit this class and implement description() and callback() method.
    * For an example of use, take a look at nel/samples/class_transport sample.
    * \author Vianney Lecroart
    * \author Nevrax France
    * \date 2002
    */
    class CTransportClass
    {
    public:
        virtual ~CTransportClass() {}
        /** Different types that we can use in a Transport class
        * warning: if you add/change a prop, change also in CTransportClass::init()
        * warning: PropUKN must be the last value (used to resize a vector)
        */
        enum TProp
        {
            PROP_UINT8, PROP_UINT16, PROP_UINT32, PROP_UINT64,
            PROP_SINT8, PROP_SINT16, PROP_SINT32, PROP_SINT64,
            PROP_BOOL , PROP_FLOAT , PROP_DOUBLE, PROP_STRING,
            PROP_DATA_SETROW, PROP_SHEET_ID, PROP_UCSTRING, PROP_SERVICE_ID ,PROP_UKN
        };

        //
        // Static methods
        //

        /// Init the transport class system (must be called one time, in the IService5::init() for example)
        static void init ();

        /// Release the transport class system (must be called one time, in the IService5::release() for example)
        static void release ();

        /** Call this function to register a new transport class.
        * \param instance A reference to a GLOBAL space of the instance of this transport class. It will be used when receive this class from network.
        */
        static void registerClass (CTransportClass &instance);

        /// Display registered transport class (debug purpose)
        static void displayLocalRegisteredClass ();


        //
        // Virtual methods
        //

        /** You have to implement this function with the description of your class. This description
        * is used to send the class accross the network and read it. It must contains a class name and
        * a set properties.
        * Example:
        *\code
        virtual void description ()
        {
        className ("SharedClass");
        property ("i1", PropUInt32, (uint32)11, i1);
        }
        *\endcode
        */
        virtual void description () = 0;

        /** This function will be call when we receive this class from the network. It will use the instance given at the
        * registration process. By default, it does nothing.
        */
        virtual void callback (const std::string &/* name */, NLNET::TServiceId /* sid */) { }


        //
        // Other methods
        //

        /// send the transport class to a specified service using the service id
        void send (NLNET::TServiceId sid);

        /// send the transport class to a specified service using the service name
        void send (const std::string &serviceName);

        /** The name of the transport class. Must be unique for each class.
        */
        void className (const std::string &name);

        /** Return the name of the transport class.
        *	The result is valid only AFTER calling of REGISTER_TRANSPORT_CLASS.
        */
        const std::string &className()		{ return Name; }

        /** One property of the class. Look description() for an example of use.
        * \param name The name of the property
        * \param type Type of the property
        * \param defaultValue The value you want to be set when a message comes without this property
        * \param value Reference to the value where the property will be read or write
        */
        template <class T> void property (const std::string &name, TProp type, T defaultValue, T &value)
        {
            switch (type)
            {
            case PROP_UINT8:
            case PROP_SINT8:
            case PROP_BOOL:
                nlassert(sizeof(T) == sizeof (uint8));
                break;
            case PROP_UINT16:
            case PROP_SINT16:
                nlassert(sizeof(T) == sizeof (uint16));
                break;
            case PROP_UINT32:
            case PROP_SINT32:
            case PROP_DATA_SETROW:
                nlassert(sizeof(T) == sizeof (uint32));
                break;
            case PROP_UINT64:
            case PROP_SINT64:
                nlassert(sizeof(T) == sizeof (uint64));
                break;
            case PROP_FLOAT:
                nlassert(sizeof(T) == sizeof (float));
                break;
            case PROP_DOUBLE:
                nlassert(sizeof(T) == sizeof (double));
                break;
            case PROP_STRING:
                nlassert(sizeof(T) == sizeof (std::string));
                break;
            case PROP_SHEET_ID:
                nlassert(sizeof(T) == sizeof (NLMISC::CSheetId));
                break;
            case PROP_UCSTRING:
                nlassert(sizeof(T) == sizeof (ucstring));
                break;
            default: nlerror ("property %s have unknown type %d", name.c_str(), type);
            }

            if (Mode == 2)          // write
            {
                // send only if needed
                // todo manage unknown prop
                TempMessage.serial (value);
            }
            else if (Mode == 3)     // register
            {
                // add a new prop to the current class
                nlassert (TempRegisteredClass.Instance != NULL);
                TempRegisteredClass.Instance->Prop.push_back (new CRegisteredProp<T> (name, type, defaultValue, &value));
            }
            else if (Mode == 4)     // display
            {
                std::string val;
                val = "defval: ";
                val += NLMISC::toString (defaultValue);
                val += " val: ";
                val += NLMISC::toString (value);
                NETTC_DEBUG ("NETTC:   prop %s %d: %s", name.c_str(), type, val.c_str());
            }
            else
            {
                nlstop;
            }
        }

        template <class T> void propertyVector (const std::string &name, TProp type, std::vector<T> &value)
        {
            if (Mode == 2)			// write
            {
                // send only if needed
                // todo manage unknown prop
                TempMessage.serialCont (value);
            }
            else if (Mode == 3)	// register
            {
                // add a new prop to the current class
                nlassert (TempRegisteredClass.Instance != NULL);
                TempRegisteredClass.Instance->Prop.push_back (new CRegisteredPropCont<std::vector<T> > (name, type, &value));
            }
            else if (Mode == 4)	// display
            {
                typedef typename std::vector<T>::iterator __iterator;
                std::string val;
                for (__iterator it = value.begin (); it != value.end(); it++)
                {
                    val += NLMISC::toString (T(*it));
                    val += " ";
                }
                NETTC_DEBUG ("NETTC:   prop %s %d: %d elements ( %s)", name.c_str(), type, value.size(), val.c_str());
            }
            else
            {
                nlstop;
            }
        }

        template <class T> void propertyCont (const std::string &name, TProp type, T &value)
        {
            if (Mode == 2)			// write
            {
                // send only if needed
                // todo manage unknown prop
                TempMessage.serialCont (value);
            }
            else if (Mode == 3)	// register
            {
                // add a new prop to the current class
                nlassert (TempRegisteredClass.Instance != NULL);
                TempRegisteredClass.Instance->Prop.push_back (new CRegisteredPropCont<T> (name, type, &value));
            }
            else if (Mode == 4)	// display
            {
                typedef typename T::iterator __iterator;
                std::string val;
                for (__iterator it = value.begin (); it != value.end(); it++)
                {
                    val += NLMISC::toString (*it);
                    val += " ";
                }
                NETTC_DEBUG ("NETTC:   prop %s %d: %d elements ( %s)", name.c_str(), type, value.size(), val.c_str());
            }
            else
            {
                nlstop;
            }
        }

        // Read the header (first part of the transport class message, currently only className)
        static void readHeader(CMessage& msgin, std::string& className)
        {
            msgin.serial(className);
        }

        /// Display with nlinfo the content of the class (debug purpose)
        void display ();

    protected:

        //
        // Structures
        //

        struct CRegisteredBaseProp
        {
            CRegisteredBaseProp () : Type( PROP_UKN ) { }
            virtual ~CRegisteredBaseProp() {}


            CRegisteredBaseProp (const std::string &name, TProp type) : Name(name), Type(type) { }

            std::string	Name;
            TProp Type;

            virtual void serialDefaultValue (NLMISC::IStream &/* f */) { }

            virtual void serialValue (NLMISC::IStream &/* f */) { }

            virtual void setDefaultValue () { }
        };

        typedef std::vector<std::pair<std::string, std::vector <CRegisteredBaseProp> > > TOtherSideRegisteredClass;

        struct CRegisteredClass
        {
            CTransportClass *Instance;

            CRegisteredClass () { clear (); }

            void clear () { Instance = NULL; }
        };

        typedef std::map<std::string, CRegisteredClass> TRegisteredClass;

        template <class T> struct CRegisteredProp : public CRegisteredBaseProp
        {
            CRegisteredProp () : Value(NULL) { }

            CRegisteredProp (const std::string &name, TProp type, T defaultValue, T *value = NULL) :
                CRegisteredBaseProp (name, type), DefaultValue(defaultValue), Value (value) { }

            T DefaultValue, *Value;

            virtual void serialDefaultValue (NLMISC::IStream &f)
            {
                f.serial (DefaultValue);
            }

            virtual void serialValue (NLMISC::IStream &f)
            {
                nlassert (Value != NULL);
                f.serial (*Value);
            }

            virtual void setDefaultValue ()
            {
                nlassert (Value != NULL);
                *Value = DefaultValue;
            }
        };

        template <class T> struct CRegisteredPropCont : public CRegisteredBaseProp
        {
            CRegisteredPropCont () : Value(NULL) { }

            CRegisteredPropCont (const std::string &name, TProp type, T *value = NULL) :
                CRegisteredBaseProp (name, type), Value (value) { }

            T *Value;

            virtual void serialDefaultValue (NLMISC::IStream &/* f */)
            {
                // nothing
            }

            virtual void serialValue (NLMISC::IStream &f)
            {
                nlassert (Value != NULL);
                f.serialCont (*Value);
            }

            virtual void setDefaultValue ()
            {
                nlassert (Value != NULL);
                Value->clear ();
            }
        };


        //
        // Variables
        //

        // Name of the class
        std::string	Name;

        // States to decode the stream from the network
        std::vector<std::vector<std::pair<sint, TProp> > > States;

        // Contains all propterties for this class
        std::vector<CRegisteredBaseProp *> Prop;


        //
        // Methods
        //

        // Read the TempMessage and call the callback
        bool read (const std::string &name, NLNET::TServiceId sid);

        // Used to create a TempMessage with this class
        NLNET::CMessage &write ();


        //
        // Static Variables
        //

        // Used to serialize unused properties from the TempMessage
        static std::vector<CRegisteredBaseProp *>	DummyProp;

        // Select what the description() must do
        static uint									Mode;	// 0=nothing 1=read 2=write 3=register 4=display

        // Contains all registered transport class
        static TRegisteredClass						LocalRegisteredClass;	// registered class that are in my program

        // The registered class that is currently filled (before put in LocalRegisteredClass)
        static CRegisteredClass						TempRegisteredClass;

        // The message that is currently filled/emptyed
        static NLNET::CMessage						TempMessage;

        static bool									Init;

        //
        // Static methods
        //

        // Called by release() to delete all structures
        static void unregisterClass ();

        // Fill the States merging local and other side class
        static void registerOtherSideClass (NLNET::TServiceId sid, TOtherSideRegisteredClass &osrc);

        // Create a message with local transport classes to send to the other side
        static void createLocalRegisteredClassMessage ();

        // Send the local transport classes to another service using the service id
        static void sendLocalRegisteredClass (NLNET::TServiceId sid)
        {
            nlassert (Init);
            NETTC_DEBUG ("NETTC: sendLocalRegisteredClass to %hu", sid.get());
            createLocalRegisteredClassMessage ();
            NLNET::CUnifiedNetwork::getInstance()->send (sid, TempMessage);
        }

        // Display a specific registered class (debug purpose)
        static void displayLocalRegisteredClass (CRegisteredClass &c);
        static void displayDifferentClass (NLNET::TServiceId sid, const std::string &className, const std::vector<CRegisteredBaseProp> &otherClass, const std::vector<CRegisteredBaseProp *> &myClass);


        //
        // Friends
        //

        friend void cbTCReceiveMessage (NLNET::CMessage &msgin, const std::string &name, NLNET::TServiceId sid);
        friend void cbTCUpService (const std::string &serviceName, NLNET::TServiceId sid, void *arg);
        friend void cbTCReceiveOtherSideClass (NLNET::CMessage &msgin, const std::string &name, NLNET::TServiceId sid);
    };



    /**
    * Get the name of message (for displaying), or extract the class name if it is a transport class.
    *
    * Preconditions:
    * - msgin is an input message that contains a valid message
    *
    * Postconditions:
    * - msgin.getPos() was modified
    * - msgName contains "msg %s" or "transport class %s" where %s is the name of message, or the name
    *   transport class is the message is a CT_MSG
    */
    void getNameOfMessageOrTransportClass( NLNET::CMessage& msgin, std::string& msgName );


    //
    // Inlines
    //

    inline void CTransportClass::className (const std::string &name)
    {
        if (Mode == 2)		// write
        {
            TempMessage.serial (const_cast<std::string &> (name));
        }
        else if (Mode == 3) // register
        {
            // add a new entry in my registered class
            nlassert (TempRegisteredClass.Instance != NULL);
            TempRegisteredClass.Instance->Name = name;
        }
        else if (Mode == 4) // display
        {
            NETTC_DEBUG ("NETTC: class %s:", name.c_str());
        }
        else
        {
            nlstop;
        }
    }


    inline void CTransportClass::send (NLNET::TServiceId sid)
    {
        nlassert (Init);
        NLNET::CUnifiedNetwork::getInstance()->send (sid, write ());
    }


    inline void CTransportClass::send (const std::string &serviceName)
    {
        nlassert (Init);
        NLNET::CUnifiedNetwork::getInstance()->send (serviceName, write ());
    }

    inline void CTransportClass::display ()
    {
        nlassert (Mode == 0);

        // set the mode to register
        Mode = 4;

        description ();

        // set to mode none
        Mode = 0;
    }

    inline NLNET::CMessage &CTransportClass::write ()
    {
        nlassert (Init);
        nlassert (Mode == 0);

#ifndef FINAL_VERSION
        // Did the programmer forget to register the transport class? Forbid sending then.
        nlassert( LocalRegisteredClass.find( className() ) != LocalRegisteredClass.end() );
#endif

        // set the mode to register
        Mode = 2;

        TempMessage.clear ();
        if (TempMessage.isReading())
            TempMessage.invert();
        TempMessage.setType ("CT_MSG");

        description ();

        // set to mode none
        Mode = 0;

        display ();

        return TempMessage;
    }

    inline bool CTransportClass::read (const std::string &name, NLNET::TServiceId sid)
    {
        nlassert (Init);
        nlassert (Mode == 0);

        // there's no info about how to read this message from this sid, give up
        if (sid.get() >= States.size())
            return false;

        // set flag of all prop

        std::vector<uint8> bitfield;
        bitfield.resize (Prop.size(), 0);

        // init prop from the stream
        uint i;
        for (i = 0; i < States[sid.get()].size(); i++)
        {
            if (States[sid.get()][i].first == -1)
            {
                // skip the value from the stream
                DummyProp[States[sid.get()][i].second]->serialDefaultValue (TempMessage);
            }
            else
            {
                // get the good value
                Prop[States[sid.get()][i].first]->serialValue (TempMessage);
                bitfield[States[sid.get()][i].first] = 1;
            }
        }

        // set default value for unknown prop
        for (i = 0; i < Prop.size(); i++)
        {
            if (bitfield[i] == 0)
            {
                Prop[i]->setDefaultValue ();
            }
        }

        display ();

        // call the user callback
        callback (name, sid);
        return true;
    }

} // NLNET

#endif // NL_TRANSPORT_CLASS_H

/* End of transport_class.h */
